/*
 * Copyright 2021 Huawei Technologies Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package org.edgegallery.developer.service.apppackage.csar.appdconverter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.edgegallery.developer.model.application.vm.VMApplication;
import org.edgegallery.developer.model.application.vm.VirtualMachine;
import org.edgegallery.developer.model.apppackage.appd.InputParam;
import org.edgegallery.developer.model.apppackage.appd.NodeTemplate;
import org.edgegallery.developer.model.apppackage.appd.TopologyTemplate;
import org.edgegallery.developer.model.apppackage.appd.vdu.SwImageData;
import org.edgegallery.developer.model.apppackage.appd.vdu.VDUCapability;
import org.edgegallery.developer.model.apppackage.appd.vdu.VDUProperty;
import org.edgegallery.developer.model.apppackage.appd.vdu.VDUStorageProperty;
import org.edgegallery.developer.model.apppackage.appd.vdu.VirtualStorageData;
import org.edgegallery.developer.model.apppackage.appd.vdu.VirtualStorageRequire;
import org.edgegallery.developer.model.apppackage.constant.AppdConstants;
import org.edgegallery.developer.model.apppackage.constant.InputConstant;
import org.edgegallery.developer.model.apppackage.constant.NodeTypeConstant;
import org.edgegallery.developer.model.resource.pkgspec.PkgSpec;
import org.edgegallery.developer.model.resource.pkgspec.PkgSpecConstants;
import org.edgegallery.developer.model.resource.vm.Flavor;
import org.edgegallery.developer.model.resource.vm.VMImage;
import org.edgegallery.developer.service.recource.pkgspec.PkgSpecService;
import org.edgegallery.developer.util.SpringContextUtil;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

public class PkgSpecsUtil {

    private PkgSpecService pkgSpecService = (PkgSpecService) SpringContextUtil.getBean(PkgSpecService.class);

    @Getter
    @Setter
    private PkgSpec pkgSpec;

    public void init(String pkgSpecId) {
        pkgSpec = pkgSpecService.getPkgSpecById(pkgSpecId);
    }

    /**
     * update vdu capability.
     */
    public void updateVduCapabilities(TopologyTemplate topologyTemplate, String vduName, NodeTemplate vduNode,
        Flavor flavor, int vduIndex) {
        if (null != pkgSpec && pkgSpec.getSpecifications() != null
            && pkgSpec.getSpecifications().getAppdSpecs() != null) {
            boolean isStorage = pkgSpec.getSpecifications().getAppdSpecs().isStorage();
            VDUCapability capability = setCapabilitiesAndInputParam(isStorage, flavor, vduIndex, vduName,
                topologyTemplate);
            vduNode.setCapabilities(capability);
        }

    }

    private VDUCapability setCapabilitiesAndInputParam(boolean storage, Flavor flavor, int vduIndex, String vduName,
        TopologyTemplate topologyTemplate) {
        VDUCapability capability;
        if ((PkgSpecConstants.PKG_SPEC_FIRST_CLOUD_DISK.equals(pkgSpec.getId())
            || PkgSpecConstants.PKG_SPEC_SECOND_CLOUD_DISK.equals(pkgSpec.getId())) && storage) {

            //set cloud-1 cloud-2 capability
            capability = new VDUCapability(flavor.getMemory() * AppdConstants.MEMORY_SIZE_UNIT, flavor.getCpu(),
                flavor.getArchitecture(), 0);
            //set cloud-1 cloud-2 input params
            setCloudDiskParam(topologyTemplate, vduIndex, flavor);

        } else if (PkgSpecConstants.PKG_SPEC_SUPPORT_FIXED_FLAVOR.equals(pkgSpec.getId())
            || PkgSpecConstants.PKG_SPEC_FIRST_LOCAL_DISK.equals(pkgSpec.getId())) {

            //set fixed local-1 capability
            capability = new VDUCapability(flavor.getMemory() * AppdConstants.MEMORY_SIZE_UNIT, flavor.getCpu(),
                flavor.getArchitecture(), flavor.getDataDiskSize());
        } else {
            //set dynamic local-2 capability and input params
            capability = setLocalDiskParam(topologyTemplate, vduIndex, flavor, vduName);

        }
        return capability;
    }

    private void setCloudDiskParam(TopologyTemplate topologyTemplate, int vduIndex, Flavor flavor) {

        InputParam appVolumeType = new InputParam(InputConstant.TYPE_STRING, "dc3_volume_type",
            InputConstant.INPUT_APP_VOLUME_TYPE);
        topologyTemplate.getInputs().put(InputConstant.INPUT_APP_VOLUME_TYPE, appVolumeType);

        String systemDiskName = InputConstant.VDU_NAME_PREFIX + vduIndex + InputConstant.INPUT_SYSTEM_DISK_POSTFIX;
        InputParam systemDisk = new InputParam(InputConstant.TYPE_INTEGER, flavor.getSystemDiskSize(), systemDiskName);
        topologyTemplate.getInputs().put(systemDiskName, systemDisk);

        String dataDiskName = InputConstant.VDU_NAME_PREFIX + vduIndex + InputConstant.INPUT_DATADISK_POSTFIX + vduIndex
            + InputConstant.VDU_SIZE_SUFFIX;
        InputParam dataDisk = new InputParam(InputConstant.TYPE_INTEGER, flavor.getDataDiskSize(), dataDiskName);
        topologyTemplate.getInputs().put(dataDiskName, dataDisk);
    }

    private VDUCapability setLocalDiskParam(TopologyTemplate topologyTemplate, int vduIndex, Flavor flavor,
        String vduName) {
        //set dynamic local-2 capability
        String memInputName = InputConstant.COMPUTE_NAME_PREFIX + InputConstant.INPUT_MEM_POSTFIX + vduIndex;
        String cpuInputName = InputConstant.COMPUTE_NAME_PREFIX + InputConstant.INPUT_VCPU_POSTFIX + vduIndex;
        String diskInputName = vduName + vduIndex + InputConstant.INPUT_DATADISK_POSTFIX + vduIndex
            + InputConstant.VDU_SIZE_SUFFIX;
        VDUCapability capability = new VDUCapability(getInputStr(memInputName), getInputStr(cpuInputName),
            flavor.getArchitecture(), getInputStr(diskInputName));
        //set dynamic local-2 input param
        InputParam memInput = new InputParam(InputConstant.TYPE_STRING,
            flavor.getMemory() * AppdConstants.MEMORY_SIZE_UNIT,
            vduName + vduIndex + InputConstant.INPUT_MEM_DES_POSTFIX);
        InputParam cpuInput = new InputParam(InputConstant.TYPE_STRING, flavor.getCpu(),
            vduName + vduIndex + InputConstant.INPUT_VCPU_DES_POSTFIX);
        InputParam diskInput = new InputParam(InputConstant.TYPE_STRING, flavor.getDataDiskSize(),
            vduName + vduIndex + InputConstant.INPUT_DATADISK_POSTFIX + vduIndex
                + InputConstant.INPUT_DATADISK_SIZE_POSTFIX);
        topologyTemplate.getInputs().put(cpuInputName, cpuInput);
        topologyTemplate.getInputs().put(memInputName, memInput);
        topologyTemplate.getInputs().put(diskInputName, diskInput);
        return capability;
    }

    /**
     * update VDU property(sw_image_data and boot_order).
     */
    public void updateProperties(VDUProperty property, VirtualMachine vm, String vduNameWithIndex,
        Map<Integer, VMImage> id2ImageMap) {
        boolean storage = pkgSpec.getSpecifications().getAppdSpecs().isStorage();
        if ((PkgSpecConstants.PKG_SPEC_FIRST_CLOUD_DISK.equals(pkgSpec.getId())
            || PkgSpecConstants.PKG_SPEC_SECOND_CLOUD_DISK.equals(pkgSpec.getId())) && storage) {
            //set property name
            if (PkgSpecConstants.PKG_SPEC_SECOND_CLOUD_DISK.equals(pkgSpec.getId())) {
                property.setName(vduNameWithIndex);
            } else {
                property.setName(vm.getName());
            }
            //set boot_oder
            Map<String, Integer> bootOrderMap = new HashMap<>();
            bootOrderMap.put(vduNameWithIndex + InputConstant.VDU_BOOT_ORDER_FIRST_STORAGE, 0);
            bootOrderMap.put(vduNameWithIndex + InputConstant.VDU_BOOT_ORDER_SECOND_STORAGE, 1);
            property.setBootOrder(bootOrderMap);
            property.setSwImageData(null);
        } else {
            property.setName(vm.getName());
            if (vm.getTargetImageId() != null) {
                property.getSwImageData().setName(id2ImageMap.get(vm.getTargetImageId()).getName());
            } else {
                property.getSwImageData().setName(id2ImageMap.get(vm.getImageId()).getName());
            }
            property.setBootOrder(null);
        }
    }

    /**
     * update VDU requirements.
     */
    public void updateVduRequirements(NodeTemplate vduNode, String vduNameWithIndex) {
        boolean storage = pkgSpec.getSpecifications().getAppdSpecs().isStorage();
        if ((PkgSpecConstants.PKG_SPEC_FIRST_CLOUD_DISK.equals(pkgSpec.getId())
            || PkgSpecConstants.PKG_SPEC_SECOND_CLOUD_DISK.equals(pkgSpec.getId())) && storage) {
            //set vdu requirements
            List<Object> list = new ArrayList<>();
            VirtualStorageRequire firstStorage = new VirtualStorageRequire();
            firstStorage.setVirtualStorage(vduNameWithIndex + InputConstant.VDU_BOOT_ORDER_FIRST_STORAGE);

            VirtualStorageRequire secondStorage = new VirtualStorageRequire();
            secondStorage.setVirtualStorage(vduNameWithIndex + InputConstant.VDU_BOOT_ORDER_SECOND_STORAGE);

            list.add(firstStorage);
            list.add(secondStorage);

            vduNode.setRequirements(list);
        }
    }

    /**
     * generate VDU storage node.
     */
    public void generateStorageNode(VMApplication application, Map<Integer, VMImage> id2ImageMap,
        TopologyTemplate topologyTemplate) {
        boolean storage = pkgSpec.getSpecifications().getAppdSpecs().isStorage();
        if ((PkgSpecConstants.PKG_SPEC_FIRST_CLOUD_DISK.equals(pkgSpec.getId())
            || PkgSpecConstants.PKG_SPEC_SECOND_CLOUD_DISK.equals(pkgSpec.getId())) && storage) {
            List<VirtualMachine> vmList = application.getVmList();
            for (int i = 0; i < vmList.size(); i++) {
                VirtualMachine vm = vmList.get(i);
                int vduIndex = i + 1;
                String vduNameWithIndex = InputConstant.VDU_NAME_PREFIX + vduIndex;
                //generate storage node-1
                generateFirstStorageNode(vduNameWithIndex, vm, id2ImageMap, topologyTemplate);
                //generate storage node-2
                generateSecondStorageNode(vduNameWithIndex, topologyTemplate, vduIndex);

            }
        }
    }

    private void generateFirstStorageNode(String vduNameWithIndex, VirtualMachine vm, Map<Integer, VMImage> id2ImageMap,
        TopologyTemplate topologyTemplate) {
        NodeTemplate firstStorageNode = new NodeTemplate();
        firstStorageNode.setType(NodeTypeConstant.NODE_TYPE_STORAGE);
        VDUStorageProperty vduStorageProperty = new VDUStorageProperty();
        vduStorageProperty.setId(getInputStr(InputConstant.INPUT_APP_VOLUME_TYPE));
        vduStorageProperty.setNfviConstraints(getInputStr(InputConstant.INPUT_NAME_AZ));
        VirtualStorageData storageData = new VirtualStorageData();
        storageData.setSizeOfStorage(getInputStr(vduNameWithIndex + InputConstant.INPUT_SYSTEM_DISK_POSTFIX));
        SwImageData swImageData = new SwImageData();
        if (vm.getTargetImageId() != null) {
            swImageData.setName(id2ImageMap.get(vm.getTargetImageId()).getName());
        } else {
            swImageData.setName(id2ImageMap.get(vm.getImageId()).getName());
        }
        vduStorageProperty.setSwImageData(swImageData);
        vduStorageProperty.setVirtualStorageData(storageData);
        firstStorageNode.setProperties(vduStorageProperty);
        String firstNodeName = vduNameWithIndex + InputConstant.VDU_BOOT_ORDER_FIRST_STORAGE;
        topologyTemplate.getNodeTemplates().put(firstNodeName, firstStorageNode);
    }

    private void generateSecondStorageNode(String vduNameWithIndex, TopologyTemplate topologyTemplate, int vduIndex) {
        NodeTemplate secondStorageNode = new NodeTemplate();
        secondStorageNode.setType(NodeTypeConstant.NODE_TYPE_STORAGE);
        VDUStorageProperty secondProperty = new VDUStorageProperty();
        secondProperty.setId(getInputStr(InputConstant.INPUT_APP_VOLUME_TYPE));
        secondProperty.setNfviConstraints(getInputStr(InputConstant.INPUT_NAME_AZ));
        VirtualStorageData secondStorageData = new VirtualStorageData();
        secondStorageData.setSizeOfStorage(getInputStr(
            vduNameWithIndex + InputConstant.INPUT_DATADISK_POSTFIX + vduIndex + InputConstant.VDU_SIZE_SUFFIX));
        secondProperty.setVirtualStorageData(secondStorageData);
        secondProperty.setSwImageData(null);
        secondStorageNode.setProperties(secondProperty);
        String secondNodeName = vduNameWithIndex + InputConstant.VDU_BOOT_ORDER_SECOND_STORAGE;
        topologyTemplate.getNodeTemplates().put(secondNodeName, secondStorageNode);
    }

    /**
     * update flavorExtra specs.
     */
    public void updateFlavorExtraSpecs(TopologyTemplate topologyTemplate, String vduName, VDUProperty property,
        String flavorExtraSpecsStr) {
        LinkedHashMap<String, String> mapSpecs = analyzeVmFlavorExtraSpecs(flavorExtraSpecsStr);
        if (null == mapSpecs) {
            return;
        }
        if (null != pkgSpec && PkgSpecConstants.PKG_SPEC_SUPPORT_FIXED_FLAVOR.equals(pkgSpec.getId())) {
            if (mapSpecs.containsKey(InputConstant.FLAVOR_EXTRA_SPECS_HOST_AGGR)) {
                String sgLabel = mapSpecs.get(InputConstant.FLAVOR_EXTRA_SPECS_HOST_AGGR);
                mapSpecs.remove(InputConstant.FLAVOR_EXTRA_SPECS_HOST_AGGR);
                mapSpecs.put(sgLabel, "true");
            }
        } else {
            if (mapSpecs.containsKey(InputConstant.FLAVOR_EXTRA_SPECS_GPU)) {
                String gpuInputName = vduName + InputConstant.INPUT_GPU_POSTFIX;
                String gpuVal = mapSpecs.get(InputConstant.FLAVOR_EXTRA_SPECS_GPU);
                InputParam gpuInput = new InputParam(InputConstant.TYPE_STRING, gpuVal, gpuInputName);
                topologyTemplate.getInputs().put(gpuInputName, gpuInput);
                mapSpecs.replace(InputConstant.FLAVOR_EXTRA_SPECS_GPU, getInputStr(gpuInputName));
            }
            if (mapSpecs.containsKey(InputConstant.FLAVOR_EXTRA_SPECS_HOST_AGGR)) {
                String hostAggrInputName = vduName + InputConstant.INPUT_HOST_AGGR_POSTFIX;
                String hostAggrLabel = mapSpecs.get(InputConstant.FLAVOR_EXTRA_SPECS_HOST_AGGR);
                InputParam hostAggrInput = new InputParam(InputConstant.TYPE_STRING, hostAggrLabel, hostAggrInputName);
                topologyTemplate.getInputs().put(hostAggrInputName, hostAggrInput);
                mapSpecs
                    .replace(InputConstant.FLAVOR_EXTRA_SPECS_HOST_AGGR, getFlavorHostAggrInputStr(hostAggrInputName));
            }
        }
        property.getVduProfile().setFlavorExtraSpecs(mapSpecs);
    }

    /**
     * update user data.
     */
    public void updateUserDataParam(TopologyTemplate topologyTemplate, Map<String, String> mapPortParams) {
        if (null == pkgSpec || PkgSpecConstants.PKG_SPEC_SUPPORT_DYNAMIC_FLAVOR.equals(pkgSpec.getId())) {
            String campusInputName = InputConstant.INPUT_CAMPUS_SEGMENT;
            InputParam campusInput = new InputParam(InputConstant.TYPE_STRING, "", campusInputName);
            if (!topologyTemplate.getInputs().containsKey(campusInputName)) {
                topologyTemplate.getInputs().put(campusInputName, campusInput);
            }
            mapPortParams.put(InputConstant.INPUT_CAMPUS_SEGMENT.toUpperCase(), getInputStr(campusInputName));
        }
    }

    private LinkedHashMap<String, String> analyzeVmFlavorExtraSpecs(String flavorExtraSpecsStr) {
        if (StringUtils.isEmpty(flavorExtraSpecsStr)) {
            return null;
        }
        //generate the definition for FlavorExtraSpecs
        Yaml yaml = new Yaml(new SafeConstructor());
        return yaml.load(flavorExtraSpecsStr);
    }

    private String getInputStr(String inputName) {
        return InputConstant.GET_INPUT_PREFIX + inputName + InputConstant.GET_INPUT_POSTFIX;
    }

    private String getFlavorHostAggrInputStr(String inputName) {
        return "[{\"{get_input:" + inputName + "}\":\"true\"}]";
    }

}
